#include "rule.h"
#include "stdlib.h"
#include "string.h"
#include "ctype.h"
#include "dictionary.h"
#include "../global/global_declarations.h"
#include "math.h"
#include "assert.h"
#include "stdio.h"

Rule* the_rule;

/**
 * makes an empty rule element
 */
RuleElement* make_rule_element() {
	RuleElement* r = calloc(1, sizeof(RuleElement));
	r->next_element = 0;
	r->digit_or_character_bound = 0;
	r->rule_type = EmptyType;
	return r;
}

/**
 * return a pointer to the first next byte in the arr
 * that doesn't contain a digit.
 */
char* go_after_the_digits(char* str) {
	while (isdigit((int) str[0])) {
		str++;
	}
	return str;
}

/**
 * creates a list of RuleElements.
 * @return the first Element in the list.
 */
RuleElement* make_list_element_from_string(String str) {
	bool first_loop = true;
	RuleElement* r, *cur_element;
	char* cur_string = str.data;
	while (cur_string[0] != 0) {
		if (first_loop) {
			r = make_rule_element();
			cur_element = r;
			first_loop = false;
		} else {
			cur_element->next_element = make_rule_element();
			cur_element = cur_element->next_element;
		}
		if (cur_string[0] == '@') {
			cur_element->rule_type = CaseSensitiveDictionaryWord;
			cur_string++;
			continue;
		}
		if (cur_string[0] == '#') {
			cur_element->rule_type = CaseInsensitiveDictionaryWord;
			cur_string++;
			continue;
		}
		if (cur_string[0] == '&') {
			cur_element->rule_type = SequenceOfDigits;
			cur_element->digit_or_character_bound = atoi(++cur_string);
			cur_string = go_after_the_digits(cur_string);
			continue;
		}
		if (cur_string[0] == '$') {
			cur_element->rule_type = SequenceOfCharacters;
			cur_element->digit_or_character_bound = atoi(++cur_string);
			cur_string = go_after_the_digits(cur_string);
			continue;
		}
	}
	return r;
}

/**
 * returns the pre-specified rule.
 * return NULL if there is none.
 */
Rule* get_the_rule() {
	return the_rule;
}

/**
 * makes an empty rule.
 * @return->next_rule = 0.
 * @return->rule_element = an empty element.
 */
Rule* make_rule() {
	Rule* r = calloc(1, sizeof(Rule));
	r->next_rule = 0;
	r->rule_element = make_rule_element();
	return r;
}

/**
 * frees rule element (and it's consecutive).
 */
void free_rule_element(RuleElement* r) {
	RuleElement* next;
	while (r != NULL) {
		next = r->next_element;
		r->digit_or_character_bound = 0;
		r->rule_type = EmptyType;
		free(r);
		r = next;
	}
}

/**
 * frees a rule (and it's consecutive).
 */
void free_rule(Rule* r) {
	Rule* next;
	while (r != NULL) {
		next = r->next_rule;
		free_rule_element(r->rule_element);
		free(r);
		r = next;
	}
}

/**
 * returns a single rule based on the string given.
 * @precondition: the string should be only the part
 * relevant to the element
 * (ends with NULL)
 */
Rule* make_single_rule_from_string(String str) {
	Rule* r = make_rule();
	r->rule_element = make_list_element_from_string(str);
	return r;
}

/**
 * creates a list of rules based on the string given.
 * each element of the list is a separate rule,
 * which was separated by "|" in the string.
 * returns a pointer to the first Rule in the list.
 */
Rule* make_rule_list_from_string(String str) {
	Rule* r, *cur_rule;
	String* str2;
	char* s = strtok(str.data, "|");
	str2 = make_empty_string();
	String_copy(str2, s);
	r = make_single_rule_from_string(*str2);
	cur_rule = r;
	s = strtok(NULL, "|");
	while (s != 0) {
		String_copy(str2, s);
		cur_rule->next_rule = make_single_rule_from_string(*str2);
		cur_rule = cur_rule->next_rule;
		s = strtok(NULL, "|");
		String_reset(str2);
	}
	String_free(str2);
	return r;
}

/**
 * prints a rule element to standard output.
 */
void print_rule_element(RuleElement* r) {
	RuleType type;
	if (r == NULL) {
		printf("NULL element.");
		return;
	}
	type = r->rule_type;
	if (type == EmptyType)
		printf("empty type rule");
	if (type == SequenceOfDigits)
		printf("sequence of %d digits", r->digit_or_character_bound);
	if (type == SequenceOfCharacters)
		printf("sequence of %d characters", r->digit_or_character_bound);
	if (type == CaseSensitiveDictionaryWord)
		printf("case sensitive word");
	if (type == CaseInsensitiveDictionaryWord)
		printf("case insensitive word");
}

/**
 * prints the rule to the standard output.
 */
void print_rule(Rule* r) {
	Rule* cur_rule;
	RuleElement* cur_element;
	cur_rule = r;
	while (cur_rule != NULL) {
		cur_element = cur_rule->rule_element;
		while (cur_element != NULL) {
			print_rule_element(cur_element);
			printf(", ");
			cur_element = cur_element->next_element;
		}
		cur_rule = cur_rule->next_rule;
		printf(" | ");
	}
}

/**
 * returns the number of words:
 * with "a" digits in the length up to "b".
 */
int64 combinatorial(int a, int b) {
	return (pow(a, b + 1) - 1) / (a - 1);
}

/**
 * returns the amount of words derives from the rule:
 * sequence of digits with the parameter n.
 */
int64 get_sequenceOfDigits_sum(int n) {
	return combinatorial(10, n);
}

/**
 * returns the amount of words derives from the rule:
 * sequence of English letters with the parameter n.
 */
int64 get_SequenceOfCharacters_sum(int n) {
	return combinatorial(52, n);
}

/**
 * return the number of words derives from the rule element.
 * @precondition: dictionary must be set.
 * if r == NULL returns 0.
 */
int64 count_words_in_rule_element(RuleElement* r, Dictionary* d) {
	RuleType type;
	RuleElement* cur = r;
	if (r == NULL)
		return 0;
	type = cur->rule_type;
	if (type == SequenceOfDigits)
		return get_sequenceOfDigits_sum(cur->digit_or_character_bound);
	if (type == SequenceOfCharacters)
		return get_SequenceOfCharacters_sum(cur->digit_or_character_bound);
	if (type == CaseSensitiveDictionaryWord)
		return d->length;
	if (type == CaseInsensitiveDictionaryWord)
		return num_of_case_insensitive_words_in_dictionary(d);
	return 0;
}

/**
 * returns the amount of words derives from the whole rule.
 * @precondition: dictionary must be initialized and set.
 */
int64 count_words_in_single_rule(Rule* r, Dictionary* d) {
	RuleElement* cur;
	int64 sum = 1;
	if (r == 0)
		return 0;
	cur = r->rule_element;
	while (cur != NULL) {
		sum *= count_words_in_rule_element(cur, d);
		cur = cur->next_element;
	}
	return sum;
}

/**
 * returns the amount of words derives from the whole rule.
 * @precondition: dictionary must be initialized and set.
 */
int64 count_words_in_whole_rule(Rule* r, Dictionary* d) {
	Rule* cur;
	int64 sum = 0;
	cur = r;
	while (cur != NULL) {
		sum += count_words_in_single_rule(cur, d);
		cur = cur->next_rule;
	}
	return sum;
}

/**
 * creates the k'th word lexicographic and puts it in dest as a string.
 * note: the lexicographic order is from left to right.
 * @precondition: dest must be initialized and empty.
 */
void create_word_k_SequenceOfCharacters(String* dest, int64 k) {
	String* str2;
	int64 j;
	int i;
	if (k == 0) {
		String_init(dest, 0);
		return;
	}
	k--;
	i = k % 52;
	str2 = make_empty_string();
	j = k / 52;
	char_int_to_string(dest, i);
	if (k >= 52) {
		create_word_k_SequenceOfCharacters(str2, j);
		String_cat_String(dest, str2);
	}
	String_free(str2);
}

/**
 * creates the k'th num lexicographic and puts it in dest as a string.
 * note: the lexicographic order is from left to right.
 * @precondition: dest must be initialized and empty.
 */
void create_word_k_SequenceOfDigits(String* dest, int64 k) {
	String* str2;
	int64 j;
	int i;
	if (k == 0) {
		String_init(dest, 0);
		return;
	}
	k--;
	i = k % 10;
	j = k / 10;
	str2 = make_empty_string();
	int_to_string_digit(dest, i);
	if (k >= 10) {
		create_word_k_SequenceOfDigits(str2, j);
		String_cat_String(dest, str2);
	}
	String_free(str2);
}

/**
 * flips or unflips the characters in the string src according to k in binary rep.
 * "1" flips lower cases to upper and upper to lower, and 0 doesn't change.
 * and puts the result in dest.
 * for example: src = "AbC" k = 110 (7) => dest = "abC".
 * @precondition: dest must be initialized and empty.
 */
void choose_case_combination_k(String* dest, int64 k, String* src) {
	int i;
	char c;
	if (k >= num_of_case_insensitive_combinations_word(*src))
		return; /*error */
	i = 0;
	String_copy_string(dest, src);
	while (k > 0) {
		while (!is_English_letter(src->data[i])) {/*go to the next letter*/
			i++;
			if (i == src->size)
				return;
		}
		c = flip_char_case(src->data[i]);
		if (k % 2 == 1)
			dest->data[i] = flip_char_case(src->data[i]);
		i++;
		k = k / 2;
	}
}

/**
 * creates the k'th word in the case insensitive word.
 * the order of the words within it combination of lower / upper is described in
 * choose_case_combination_k.
 * @precondition: dest must be initialized and empty.
 */
void create_word_k_CaseInsensitiveDictionaryWord(String* dest, int64 k,
		Dictionary* d) {
	int64 i, cur_index, cur_combinas;
	DictionaryRecord cur_record;
	if (d == NULL || d->length == 0) {
		String_init(dest, 0);
		return;
	}
	for (i = 0; i < d->length; ++i) {
		cur_record = d->words_arr[i];
		cur_combinas = num_of_case_insensitive_combinations_word(
				cur_record.word);
		cur_index = cur_record.index + cur_combinas;
		if (cur_index > k) {
			choose_case_combination_k(dest, k - cur_record.index,
					&cur_record.word);
			return;
		}
	}
}

/**
 * finds the k'th word derived from the single rule element and puts in dest.
 * @precondition: dest must be initialized and empty.
 */
void get_word_k_for_single_element(String* dest, int64 k, RuleElement* r,
		Dictionary* d) {
	DictionaryRecord* words_arr;
	int64 l;
	RuleType type = r->rule_type;
	words_arr = d->words_arr;
	l = d->length;
	if (type == SequenceOfDigits) {
		create_word_k_SequenceOfDigits(dest, k);
	}
	if (type == SequenceOfCharacters) {
		create_word_k_SequenceOfCharacters(dest, k);
	}
	if (type == CaseSensitiveDictionaryWord) {
		String_copy_string(dest, &words_arr[k].word);
	}
	if (type == CaseInsensitiveDictionaryWord) {
		create_word_k_CaseInsensitiveDictionaryWord(dest, k, d);
	}
}

/**
 * gets the k'th word derives from the single rule and puts in dest.
 * @precondition: k < num of words derived from the single rule.
 * @precondition: r != NULL
 * @precondition: dictionary must be initialized and set.
 * @precondition: dest must be initialized and empty.
 */
void get_word_k_for_single_rule(String* dest, int64 k, Rule* r, Dictionary* d) {
	String* temp;
	int64 element_size, c;
	RuleElement* cur;
	assert(r != NULL);
	assert(k < count_words_in_single_rule(r, d));
	temp = make_empty_string();
	cur = r->rule_element;
	while (cur != NULL) {
		element_size = count_words_in_rule_element(cur, d);
		c = k % element_size;
		get_word_k_for_single_element(temp, c, cur, d);
		String_cat_String(dest, temp);
		k = k / element_size;
		cur = cur->next_element;
		String_reset(temp);
	}
	String_free(temp);
}

/**
 * returns the k'th word derived from the pre-specified rule and dictionary.
 * @precondition: the dictionary and the rule must be initialized and set.
 */
void word_k_for_rule(int64 k, String* dest) {
	Rule* r, *cur_rule;
	int64 rule_size, cur_rule_size;
	Dictionary* d = get_the_dictionary();
	r = get_the_rule();
	rule_size = count_words_in_whole_rule(r, d);
	if (k >= rule_size)
		return;
	cur_rule = r;

	while (cur_rule != 0) {
		cur_rule_size = count_words_in_single_rule(cur_rule, d);
		if (k < cur_rule_size) {
			get_word_k_for_single_rule(dest, k, cur_rule, d);
			break;
		}
		cur_rule = cur_rule->next_rule;
		k -= cur_rule_size;
	}
}

/**
 * checks that char* is in the right format to become a rule.
 */
bool validate_rule(char* r) {
	int cur_index = 0;
	char cur;
	bool go_on_with_loop = true, last_was_pipe = false;
	while (r[cur_index] != 0) {
		cur = r[cur_index];
		if (cur == '|') {
			if (last_was_pipe) {
				return false;
			}
			last_was_pipe = true;
		} else {
			last_was_pipe = false;
		}
		if ((cur == '@') || (cur == '#') || (cur == '|')) {
			cur_index++;
			continue;
		}
		if (cur == '&' || (cur == '$')) {
			cur_index++;
			if (!isdigit((int)r[cur_index])) {
				fprintf(stderr, "Error: rule %s does not fit syntax.\n", r);
				return false;
			}
			go_on_with_loop = true;
			while (go_on_with_loop) {
				cur_index++;
				if (r[cur_index] == 0)
					return true;

				if (!isdigit((int)r[cur_index])) {
					go_on_with_loop = false;
				}
			}
			continue;
		} else {
			return false;
		}
	}
	if (last_was_pipe)
		return false;
	return true;
}
